// ****************************************************************
// Copyright 2012, Stephan Burguchev
// e-mail: me@sburg.net
// ****************************************************************
// *
using System;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using Ionic.Zip;
using Updater.Common;
using Updater.Sources;
using Updater.Utils;

namespace Updater.Updates
{
    public class ZipArchiveUpdate : IUpdate
    {
        public Configuration Configuration { get; set; }
        public UpdateInformation Information { get; set; }

        public ISource Source { get; set; }

        public void Apply()
        {
            try
            {
                string patchDataDir = Path.Combine(this.Configuration.UpdaterCacheDirectory, this.getUpdateUniqueName());
                string appDir = this.Configuration.ApplicationDirectory;

                FileSystem.CopyDirectory(patchDataDir, appDir, true);
            }
            catch (Exception ex)
            {
                throw new UpdaterException(string.Format("Unable to apply the update {0}.", this.getUpdateVersion()), ex);
            }
        }

        public bool CheckIfElevationRequired()
        {
            try
            {
                const FileSystemRights rightsToCheck = FileSystemRights.Modify;
                return !FileSystem.CheckForFileSystemRightsOnDirectory(
                    this.Configuration.ApplicationDirectory,
                    rightsToCheck,
                    true);
            }
            catch (Exception ex)
            {
                throw new UpdaterException(
                    string.Format("Failed to check required permissions for the update {0}.", this.getUpdateVersion()),
                    ex);
            }
        }

        public void Download(Action<DownloadProgressInfo> downloadInfo)
        {
            this.checkPreconditions();

            try
            {
                Directory.CreateDirectory(this.Configuration.UpdaterCacheDirectory);
                string zipArchivePath = Path.Combine(
                    this.Configuration.UpdaterCacheDirectory, this.getUpdateUniqueName() + ".zip");

                if (File.Exists(zipArchivePath) && this.Information.Checksum != null)
                {
                    byte[] actualHash = ChecksumHelper.ComputeFileChecksum(zipArchivePath, this.Information.ChecksumType.Value);
                    if (actualHash.SequenceEqual(this.Information.Checksum))
                    {
                        // The file has been downloaded earlier and the checksum is valid.
                        return;
                    }
                }

                using (FileStream zipArchive = File.Create(zipArchivePath))
                {
                    this.Source.Download(
                        this.Information.Location,
                        zipArchive,
                        delegate(DownloadProgressInfo info)
                        {
                            if (downloadInfo != null)
                            {
                                downloadInfo(info);
                            }
                        });
                }
            }
            catch (Exception ex)
            {
                throw new UpdaterException(string.Format("Failed to download the update {0}.", this.getUpdateVersion()), ex);
            }
        }

        public void Prepare()
        {
            try
            {
                Directory.CreateDirectory(this.Configuration.UpdaterCacheDirectory);
                string zipArchivePath = Path.Combine(
                    this.Configuration.UpdaterCacheDirectory, this.getUpdateUniqueName() + ".zip");

                if (!File.Exists(zipArchivePath))
                {
                    throw new FileNotFoundException("Could not find the zip-archive file.", zipArchivePath);
                }

                string extractedDataPath = Path.Combine(this.Configuration.UpdaterCacheDirectory, this.getUpdateUniqueName());
                if (Directory.Exists(extractedDataPath))
                {
                    Directory.Delete(extractedDataPath, true);
                }

                Directory.CreateDirectory(extractedDataPath);

                using (ZipFile zipFile = ZipFile.Read(zipArchivePath))
                {
                    zipFile.ExtractAll(extractedDataPath, ExtractExistingFileAction.OverwriteSilently);
                }
            }
            catch (Exception ex)
            {
                throw new UpdaterException(string.Format("Unable to prepare the update {0}.", this.getUpdateVersion()), ex);
            }
        }

        private void checkPreconditions()
        {
            if (this.Information == null)
            {
                throw new InvalidOperationException("The Information property is null.");
            }

            if (this.Source == null)
            {
                throw new InvalidOperationException("The Source property is null.");
            }

            if (this.Configuration == null)
            {
                throw new InvalidOperationException("The Configuration property is null.");
            }
        }

        private string getUpdateUniqueName()
        {
            return string.Format("update-{0}", this.Information.Version);
        }

        private string getUpdateVersion()
        {
            if (this.Information != null && this.Information.Version != null)
            {
                return this.Information.Version.ToString();
            }

            return "?.?.?.?";
        }
    }
}
